﻿using System;
using System.Numerics;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Microsoft.Basetypes;

namespace Microsoft.Boogie.AbstractInterpretation
{
  class NativeIntervallDomain : NativeLattice
  {
    abstract class E_Common : NativeLattice.Element { }
    class E_Bottom : E_Common
    {
      public override Expr ToExpr() {
        return Expr.False;
      }
    }
    class E : E_Common
    {
      public readonly Node N;
      public E() { }
      public E(Node n) {
        N = n;
      }

      public override Expr ToExpr() {
        Expr expr = Expr.True;
        for (var n = N; n != null; n = n.Next) {
          expr = BplAnd(expr, n.ToExpr());
        }
        return expr;
      }
    }
    public class Node
    {
      public readonly Variable V;  // variable has type bool or int
      // For an integer variable (Lo,Hi) indicates Lo <= V < Hi, where Lo==null means no lower bound and Hi==null means no upper bound
      // For a boolean variable, (Lo,Hi) is one of: (null,null) for {false,true}, (null,1) for {false}, and (1,null) for {true}.
      public readonly BigInteger? Lo;
      public readonly BigInteger? Hi;
      public Node Next;  // always sorted according to StrictlyBefore; readonly after full initialization
      public static bool StrictlyBefore(Variable a, Variable b) {
        Contract.Assert(a.UniqueId != b.UniqueId || a == b);
        return a.UniqueId < b.UniqueId;
      }

      Node(Variable v, BigInteger? lo, BigInteger? hi, Node next) {
        Contract.Requires(lo != null || hi != null);  // don't accept empty constraints
        Contract.Requires(next == null || StrictlyBefore(v, next.V));
        V = v;
        Lo = lo;
        Hi = hi;
        Next = next;
      }

      /// <summary>
      /// This constructor leaves Next as null, allowing the caller to fill in Next to finish off the construction.
      /// </summary>
      public Node(Variable v, BigInteger? lo, BigInteger? hi) {
        Contract.Requires(lo != null || hi != null);  // don't accept empty constraints
        V = v;
        Lo = lo;
        Hi = hi;
      }

      /// <summary>
      /// Returns a Node that has the constraints head.{V,Lo,Hi} plus
      /// all the constraints entailed by Nodes reachable from tail.
      /// Requires that "head" sorts no later than anything in "tail".
      /// Create either returns "head" itself or returns a new Node.
      /// </summary>
      public static Node Create(Node head, Node tail) {
        Contract.Requires(head != null);
        Contract.Requires(tail == null || !StrictlyBefore(tail.V, head.V));
        Contract.Requires(head != tail);

        if (head.Next == tail) {
          return head;
        } else if (tail != null && head.V == tail.V) {
          // incorporate both constraints into one Node
          return new Node(head.V, Max(head.Lo, tail.Lo, true), Min(head.Lo, tail.Lo, true), tail.Next);
        } else {
          return new Node(head.V, head.Lo, head.Hi, tail);
        }
      }

      public static void GetBounds(Node n, Variable v, out BigInteger? lo, out BigInteger? hi) {
        for (; n != null; n = n.Next) {
          if (n.V == v) {
            lo = n.Lo;
            hi = n.Hi;
            return;
          } else if (StrictlyBefore(v, n.V)) {
            break;
          }
        }
        lo = null;
        hi = null;
      }

      /// <summary>
      /// Return the minimum of "a" and "b".  If treatNullAsUnit==true, then "null" is
      /// interpreted as positive infinity (the unit element of min); otherwise, it is
      /// treated as negative infinity (the zero element of min).
      /// </summary>
      public static BigInteger? Min(BigInteger? a, BigInteger? b, bool treatNullAsUnit) {
        if (a == null) {
          return treatNullAsUnit ? b : a;
        } else if (b == null) {
          return treatNullAsUnit ? a : b;
        } else {
          return BigInteger.Min((BigInteger)a, (BigInteger)b);
        }
      }

      /// <summary>
      /// Return the maximum of "a" and "b".  If treatNullAsUnit==true, then "null" is
      /// interpreted as negative infinity (the unit element of max); otherwise, it is
      /// treated as positive infinity (the zero element of max).
      /// </summary>
      public static BigInteger? Max(BigInteger? a, BigInteger? b, bool treatNullAsUnit) {
        if (a == null) {
          return treatNullAsUnit ? b : a;
        } else if (b == null) {
          return treatNullAsUnit ? a : b;
        } else {
          return BigInteger.Max((BigInteger)a, (BigInteger)b);
        }
      }

      public static IEnumerable<Tuple<Node, Node>> Merge(Node a, Node b) {
        while (true) {
          if (a == null && b == null) {
            yield break;
          } else if (a == null || b == null) {
            yield return new Tuple<Node, Node>(a, b);
            if (a != null) { a = a.Next; } else { b = b.Next; }
          } else if (a.V == b.V) {
            yield return new Tuple<Node, Node>(a, b);
            a = a.Next; b = b.Next;
          } else if (StrictlyBefore(a.V, b.V)) {
            yield return new Tuple<Node, Node>(a, null);
            a = a.Next;
          } else {
            yield return new Tuple<Node, Node>(null, b);
            b = b.Next;
          }
        }
      }

      public Expr ToExpr() {
        if (!V.IsMutable && CommandLineOptions.Clo.InstrumentInfer != CommandLineOptions.InstrumentationPlaces.Everywhere) {
          // omit invariants about readonly variables
          return Expr.True;
        } else  if (V.TypedIdent.Type.IsBool) {
          if (Lo == null && Hi == null) {
            return Expr.True;
          } else {
            Contract.Assert((Lo == null && (BigInteger)Hi == 1) || (Hi == null && (BigInteger)Lo == 1));
            var ide = new IdentifierExpr(Token.NoToken, V);
            return Hi == null ? ide : Expr.Not(ide);
          }
        } else {
          Expr e = Expr.True;
          if (Lo != null && Hi != null && Lo + 1 == Hi) {
            // produce an equality
            var ide = new IdentifierExpr(Token.NoToken, V);
            e = Expr.And(e, BplEq(ide, Expr.Literal(Basetypes.BigNum.FromBigInt((BigInteger)Lo))));
          } else {
            // produce a (possibly empty) conjunction of inequalities
            if (Lo != null) {
              var ide = new IdentifierExpr(Token.NoToken, V);
              e = Expr.And(e, BplLe(Expr.Literal(Basetypes.BigNum.FromBigInt((BigInteger)Lo)), ide));
            }
            if (Hi != null) {
              var ide = new IdentifierExpr(Token.NoToken, V);
              e = Expr.And(e, BplLt(ide, Expr.Literal(Basetypes.BigNum.FromBigInt((BigInteger)Hi))));
            }
          }
          return e;
        }
      }
    }

    List<BigInteger> upThresholds;  // invariant: thresholds are sorted
    List<BigInteger> downThresholds;  // invariant: thresholds are sorted

    /// <summary>
    /// Requires "thresholds" to be sorted.
    /// </summary>
    public NativeIntervallDomain() {
      upThresholds = new List<BigInteger>();
      downThresholds = new List<BigInteger>();
    }

    public override void Specialize(Implementation impl) {
      if (impl == null) {
        // remove thresholds
        upThresholds = new List<BigInteger>();
        downThresholds = new List<BigInteger>();
      } else {
        var tf = new ThresholdFinder(impl);
        tf.Find(out downThresholds, out upThresholds);
#if DEBUG_PRINT
        Console.Write("DEBUG: for implementation '{0}', setting downs to [", impl.Name);
        foreach (var i in downThresholds) {
          Console.Write(" {0}", i);
        }
        Console.Write(" ] and ups to [");
        foreach (var i in upThresholds) {
          Console.Write(" {0}", i);
        }
        Console.WriteLine(" ]");
#endif
      }
      base.Specialize(impl);
    }

    private E_Common top = new E();
    private E_Common bottom = new E_Bottom();

    public override Element Top { get { return top; } }
    public override Element Bottom { get { return bottom; } }

    public override bool IsTop(Element element) {
      var e = element as E;
      return e != null && e.N == null;
    }
    public override bool IsBottom(Element element) {
      return element is E_Bottom;
    }

    public override bool Below(Element a, Element b) {
      if (a is E_Bottom) {
        return true;
      } else if (b is E_Bottom) {
        return false;
      } else {
        var aa = (E)a;
        var bb = (E)b;
        // check if every constraint in 'bb' is implied by constraints in 'aa'
        foreach (var t in Node.Merge(aa.N, bb.N)) {
          var x = t.Item1;
          var y = t.Item2;
          if (x == null) {
            // bb constrains a variable that aa does not
            return false;
          } else if (y == null) {
            // aa constrains a variable that bb does not; that's fine
          } else if (y.Lo != null && (x.Lo == null || x.Lo < y.Lo)) {
            // bb has a Lo constraint, and either aa has no Lo constraint or it has a weaker Lo constraint
            return false;
          } else if (y.Hi != null && (x.Hi == null || y.Hi < x.Hi)) {
            // bb has a Hi o constraint, and either aa has no Hi constraint or it has a weaker Hi constraint
            return false;
          }
        }
        return true;
      }
    }

    public override Element Meet(Element a, Element b) {
      if (a is E_Bottom) {
        return a;
      } else if (b is E_Bottom) {
        return b;
      } else {
        var aa = (E)a;
        var bb = (E)b;
        Node head = null;
        Node prev = null;
        foreach (var t in Node.Merge(aa.N, bb.N)) {
          var x = t.Item1;
          var y = t.Item2;
          Node n;
          if (x == null) {
            n = new Node(y.V, y.Lo, y.Hi);
          } else if (y == null) {
            n = new Node(x.V, x.Lo, x.Hi);
          } else {
            var lo = Node.Max(x.Lo, y.Lo, true);
            var hi = Node.Min(x.Hi, y.Hi, true);
            // if hi<=lo, then we're overconstrained
            if (lo != null && hi != null && hi <= lo) {
              return bottom;
            }
            n = new Node(x.V, lo, hi);
          }
          if (head == null) {
            head = n;
          } else {
            prev.Next = n;
          }
          prev = n;
        }
        return new E(head);
      }
    }

    public override Element Join(Element a, Element b) {
      if (a is E_Bottom) {
        return b;
      } else if (b is E_Bottom) {
        return a;
      } else {
        var aa = (E)a;
        var bb = (E)b;
        // for each variable, take the weaker of the constraints
        Node head = null;
        Node prev = null;
        foreach (var t in Node.Merge(aa.N, bb.N)) {
          if (t.Item1 != null && t.Item2 != null) {
            var lo = Node.Min(t.Item1.Lo, t.Item2.Lo, false);
            var hi = Node.Max(t.Item1.Hi, t.Item2.Hi, false);
            if (lo != null || hi != null) {
              var n = new Node(t.Item1.V, lo, hi);
              if (head == null) {
                head = n;
              } else {
                prev.Next = n;
              }
              prev = n;
            }
          }
        }
        return new E(head);
      }
    }

    public override Element Widen(Element a, Element b) {
      if (a is E_Bottom) {
        return b;  // since this is done just once, we maintain the ascending chains property
      } else if (b is E_Bottom) {
        return a;
      } else {
        var aa = (E)a;
        var bb = (E)b;
        // return a subset of the constraints of aa, namely those that are implied by bb
        Node head = null;
        Node prev = null;
        foreach (var t in Node.Merge(aa.N, bb.N)) {
          var x = t.Item1;
          var y = t.Item2;
          if (x != null && y != null) {
            BigInteger? lo, hi;
            lo = hi = null;
            if (x.Lo != null && y.Lo != null) {
              if (x.Lo <= y.Lo) {
                // okay, we keep the lower bound
                lo = x.Lo;
              } else {
                // set "lo" to the threshold that is below (or equal) y.Lo
                lo = RoundDown((BigInteger)y.Lo);
              }
            }
            if (x.Hi != null && y.Hi != null) {
              if (y.Hi <= x.Hi) {
                // okay, we keep the upper bound
                hi = x.Hi;
              } else {
                // set "hi" to the threshold that is above (or equal) y.Hi
                hi = RoundUp((BigInteger)y.Hi);
              }
            }
            if (lo != null || hi != null) {
              var n = new Node(x.V, lo, hi);
              if (head == null) {
                head = n;
              } else {
                prev.Next = n;
              }
              prev = n;
            }
          }
        }
        return new E(head);
      }
    }

    /// <summary>
    /// For a proof of correctness of this method, see Test/dafny2/Intervals.dfy.
    /// A difference is that the this method returns:
    ///     let d = Dafny_RoundDown(k);
    ///     return d == -1 ? null : downThresholds[d];
    /// </summary>
    BigInteger? RoundDown(BigInteger k)
    {
      if (downThresholds.Count == 0 || k < downThresholds[0]) {
        return null;
      }
      var i = 0;
      var j = downThresholds.Count - 1;
      while (i < j)
      {
        var mid = i + (j - i + 1) / 2;
        if (downThresholds[mid] <= k) {
          i = mid;
        } else {
          j = mid - 1;
        }
      }
      return downThresholds[i];
    }

    /// <summary>
    /// For a proof of correctness of this method, see Test/dafny2/Intervals.dfy.
    /// A difference is that the this method returns:
    ///     let d = Dafny_RoundUp(k);
    ///     return d == thresholds.Count ? null : upThresholds[d];
    /// </summary>
    BigInteger? RoundUp(BigInteger k)
    {
      if (upThresholds.Count == 0 || upThresholds[upThresholds.Count - 1] < k) {
        return null;
      }
      var i = 0;
      var j = upThresholds.Count - 1;
      while (i < j)
      {
        var mid = i + (j - i) / 2;
        if (upThresholds[mid] < k) {
          i = mid + 1;
        } else {
          j = mid;
        }
      }
      return upThresholds[i];
    }

    public override Element Constrain(Element element, Expr expr) {
      if (element is E_Bottom) {
        return element;
      } else {
        var e = (E)element;
        var c = Constraint(expr, e.N);
        return c == null ? element : Meet(element, c);
      }
    }

    /// <summary>
    /// Returns an Element that corresponds to the constraints implied by "expr" in the
    /// state "state".
    /// Return "null" to indicate no constraints.
    /// </summary>
    E_Common Constraint(Expr expr, Node state) {
      Variable v;
      if (IsVariable(expr, out v)) {
        var n = new Node(v, new BigInteger(1), null);
        return new E(n);
      } else if (expr is LiteralExpr) {
        var e = (LiteralExpr)expr;
        return (bool)e.Val ? null : new E_Bottom();
      } else if (expr is NAryExpr) {
        var e = (NAryExpr)expr;
        if (e.Fun is UnaryOperator) {
          if (((UnaryOperator)e.Fun).Op == UnaryOperator.Opcode.Not) {
            if (IsVariable(e.Args[0], out v)) {
              var n = new Node(v, null, new BigInteger(1));
              return new E(n);
            }
          }
        } else if (e.Fun is BinaryOperator) {
          var op = ((BinaryOperator)e.Fun).Op;
          var arg0 = e.Args[0];
          var arg1 = e.Args[1];
          switch (op) {
            case BinaryOperator.Opcode.Eq:
            case BinaryOperator.Opcode.Iff: {
                E c = null;
                if (IsVariable(arg0, out v)) {
                  BigInteger? lo, hi;
                  if (PartiallyEvaluate(arg1, state, out lo, out hi)) {
                    var n = new Node(v, lo, hi);
                    c = new E(n);
                  }
                }
                if (IsVariable(arg1, out v)) {
                  BigInteger? lo, hi;
                  if (PartiallyEvaluate(arg1, state, out lo, out hi)) {
                    var n = new Node(v, lo, hi);
                    c = c == null ? new E(n) : (E)Meet(c, new E(n));
                  }
                }
                return c;
              }
            case BinaryOperator.Opcode.Neq: {
                E c = null;
                if (IsVariable(arg0, out v)) {
                  c = ConstrainNeq(state, v, arg1);
                }
                if (IsVariable(arg1, out v)) {
                  var cc = ConstrainNeq(state, v, arg0);
                  if (cc != null) {
                    c = c == null ? cc : (E)Meet(c, cc);
                  }
                }
                return c;
              }
            case BinaryOperator.Opcode.Le: {
                E c = null;
                if (IsVariable(arg1, out v)) {
                  BigInteger? lo, hi;
                  PartiallyEvaluate(arg0, state, out lo, out hi);
                  if (lo != null) {
                    var n = new Node(v, lo, null);
                    c = new E(n);
                  }
                }
                if (IsVariable(arg0, out v)) {
                  BigInteger? lo, hi;
                  PartiallyEvaluate(arg1, state, out lo, out hi);
                  if (hi != null) {
                    var n = new Node(v, null, hi);
                    c = c == null ? new E(n) : (E)Meet(c, new E(n));
                  }
                }
                return c;
              }
            case BinaryOperator.Opcode.Lt: {
                E c = null;
                if (IsVariable(arg1, out v)) {
                  BigInteger? lo, hi;
                  PartiallyEvaluate(arg0, state, out lo, out hi);
                  if (lo != null) {
                    var n = new Node(v, lo + 1, null);
                    c = new E(n);
                  }
                }
                if (IsVariable(arg0, out v)) {
                  BigInteger? lo, hi;
                  PartiallyEvaluate(arg1, state, out lo, out hi);
                  if (hi != null) {
                    var n = new Node(v, null, hi - 1);
                    c = c == null ? new E(n) : (E)Meet(c, new E(n));
                  }
                }
                return c;
              }
            case BinaryOperator.Opcode.Ge: {
                var tmp = arg0; arg0 = arg1; arg1 = tmp;
                goto case BinaryOperator.Opcode.Le;
              }
            case BinaryOperator.Opcode.Gt: {
                var tmp = arg0; arg0 = arg1; arg1 = tmp;
                goto case BinaryOperator.Opcode.Lt;
              }
            default:
              break;
          }
        }
      }
      return null;  // approximation
    }

    private E ConstrainNeq(Node state, Variable v, Expr arg) {
      BigInteger? lo, hi;
      if (PartiallyEvaluate(arg, state, out lo, out hi)) {
        if (lo != null && hi != null && lo + 1 == hi) {
          var exclude = lo;
          // If the partially evaluated arg (whose value is "exclude") is an end-point of
          // the interval known for "v", then produce a constraint that excludes that bound.
          Node.GetBounds(state, v, out lo, out hi);
          if (lo != null && lo == exclude) {
            var n = new Node(v, lo + 1, null);
            return new E(n);
          } else if (hi != null && exclude + 1 == hi) {
            var n = new Node(v, null, exclude);
            return new E(n);
          }
        }
      }
      return null;
    }

    bool IsVariable(Expr expr, out Variable v) {
      var e = expr as IdentifierExpr;
      if (e == null) {
        v = null;
        return false;
      } else {
        v = e.Decl;
        return true;
      }
    }

    public override Element Update(Element element, AssignCmd cmd) {
      if (element is E_Bottom) {
        return element;
      }
      var e = (E)element;
      var nn = e.N;
      Contract.Assert(cmd.Lhss.Count == cmd.Rhss.Count);
      for (int i = 0; i < cmd.Lhss.Count; i++) {
        var lhs = cmd.Lhss[i];
        var rhs = cmd.Rhss[i];
        BigInteger? lo;
        BigInteger? hi;
        PartiallyEvaluate(rhs, e.N, out lo, out hi);
        nn = UpdateOne(nn, lhs.DeepAssignedVariable, lo, hi);
      }
      return new E(nn);
    }

    bool PartiallyEvaluate(Expr rhs, Node node, out BigInteger? lo, out BigInteger? hi) {
      var pe = new PEVisitor(node);
      pe.VisitExpr(rhs);
      lo = pe.Lo;
      hi = pe.Hi;
      return lo != null || hi != null;
    }

    class PEVisitor : StandardVisitor
    {
      public BigInteger? Lo;
      public BigInteger? Hi;

      readonly BigInteger one = new BigInteger(1);

      Node N;
      public PEVisitor(Node n) {
        N = n;
      }

      // Override visitors for all expressions that can return a boolean or integer result

      public override Expr VisitExpr(Expr node) {
        Lo = Hi = null;
        return base.VisitExpr(node);
      }
      public override LiteralExpr VisitLiteralExpr(LiteralExpr node) {
        if (node.Val is BigNum) {
          var n = ((BigNum)node.Val).ToBigInteger;
          Lo = n;
          Hi = n + 1;
        } else if (node.Val is BigDec) {
          var n = ((BigDec)node.Val).Floor(-BigInteger.Pow(10, 12), BigInteger.Pow(10, 12));
          Lo = n;
          Hi = n + 1;
        } else if (node.Val is bool) {
          if ((bool)node.Val) {
            // true
            Lo = one;
            Hi = null;
          } else {
            // false
            Lo = null;
            Hi = one;
          }
        }
        return node;
      }
      public override Expr VisitIdentifierExpr(IdentifierExpr node) {
        if (node.Type.IsBool || node.Type.IsInt || node.Type.IsReal) {
          Node.GetBounds(N, node.Decl, out Lo, out Hi);
        }
        return node;
      }
      public override Expr VisitNAryExpr(NAryExpr node) {
        if (node.Fun is UnaryOperator) {
          var op = (UnaryOperator)node.Fun;
          Contract.Assert(node.Args.Length == 1);
          if (op.Op == UnaryOperator.Opcode.Neg) {
            BigInteger? lo, hi;
            VisitExpr(node.Args[0]);
            lo = Lo; hi = Hi;
            if (hi != null) {
              Lo = 1 - hi;
            }
            if (lo != null) {
              Hi = -lo;
            }
          }
          else if (op.Op == UnaryOperator.Opcode.Not) {
            VisitExpr(node.Args[0]);
            Contract.Assert((Lo == null && Hi == null) ||
              (Lo == null && (BigInteger)Hi == 1) ||
              (Hi == null && (BigInteger)Lo == 1));
            var tmp = Lo;
            Lo = Hi;
            Hi = tmp;
          }
        } else if (node.Fun is BinaryOperator) {
          var op = (BinaryOperator)node.Fun;
          Contract.Assert(node.Args.Length == 2);
          BigInteger? lo0, hi0, lo1, hi1;
          VisitExpr(node.Args[0]);
          lo0 = Lo; hi0 = Hi;
          VisitExpr(node.Args[1]);
          lo1 = Lo; hi1 = Hi;
          Lo = Hi = null;
          switch (op.Op) {
            case BinaryOperator.Opcode.And:
              if (hi0 != null || hi1 != null) {
                // one operand is definitely false, thus so is the result
                Lo = null; Hi = one;
              } else if (lo0 != null && lo1 != null) {
                // both operands are definitely true, thus so is the result
                Lo = one; Hi = null;
              }
              break;
            case BinaryOperator.Opcode.Or:
              if (lo0 != null || lo1 != null) {
                // one operand is definitely true, thus so is the result
                Lo = one; Hi = null;
              } else if (hi0 != null && hi1 != null) {
                // both operands are definitely false, thus so is the result
                Lo = null; Hi = one;
              }
              break;
            case BinaryOperator.Opcode.Imp:
              if (hi0 != null || lo1 != null) {
                // either arg0 false or arg1 is true, so the result is true
                Lo = one; Hi = null;
              } else if (lo0 != null && hi1 != null) {
                // arg0 is true and arg1 is false, so the result is false
                Lo = null; Hi = one;
              }
              break;
            case BinaryOperator.Opcode.Iff:
              if (lo0 != null && lo1 != null) {
                Lo = one; Hi = null;
              } else if (hi0 != null && hi1 != null) {
                Lo = one; Hi = null;
              } else if (lo0 != null && hi1 != null) {
                Lo = null; Hi = one;
              } else if (hi0 != null && lo1 != null) {
                Lo = null; Hi = one;
              }
              if (op.Op == BinaryOperator.Opcode.Neq) {
                var tmp = Lo; Lo = Hi; Hi = tmp;
              }
              break;
            case BinaryOperator.Opcode.Eq:
            case BinaryOperator.Opcode.Neq:
              if (node.Args[0].Type.IsBool) {
                goto case BinaryOperator.Opcode.Iff;
              }
              // For Eq:
              // If the (lo0,hi0) and (lo1,hi1) ranges do not overlap, the answer is false.
              // If both ranges are the same unit range, then the answer is true.
              if (hi0 != null && lo1 != null && hi0 <= lo1) {
                Lo = null; Hi = one;
              } else if (lo0 != null && hi1 != null && hi1 <= lo0) {
                Lo = null; Hi = one;
              } else if (lo0 != null && hi0 != null && lo1 != null && hi1 != null &&
                lo0 + 1 == hi0 && lo0 == lo1 && lo1 + 1 == hi1) {
                Lo = one; Hi = null;
              }
              if (op.Op == BinaryOperator.Opcode.Neq) {
                var tmp = Lo; Lo = Hi; Hi = tmp;
              }
              break;
            case BinaryOperator.Opcode.Le:
            case BinaryOperator.Opcode.Gt:
              // If hi0 - 1 <= lo1, then the answer is true.
              // If hi1 <= lo0, then the answer is false.
              if (hi0 != null && lo1 != null && hi0 - 1 <= lo1) {
                Lo = one; Hi = null;
              } else if (lo0 != null && hi1 != null && hi1 <= lo0) {
                Lo = null; Hi = one;
              }
              if (op.Op == BinaryOperator.Opcode.Gt) {
                var tmp = Lo; Lo = Hi; Hi = tmp;
              }
              break;
            case BinaryOperator.Opcode.Lt:
            case BinaryOperator.Opcode.Ge:
              // If hi0 <= lo1, then the answer is true.
              // If hi1 - 1 <= lo0, then the answer is false.
              if (hi0 != null && lo1 != null && hi0 <= lo1) {
                Lo = one; Hi = null;
              } else if (lo0 != null && hi1 != null && hi1 - 1 <= lo0) {
                Lo = null; Hi = one;
              }
              if (op.Op == BinaryOperator.Opcode.Ge) {
                var tmp = Lo; Lo = Hi; Hi = tmp;
              }
              break;
            case BinaryOperator.Opcode.Add:
              if (lo0 != null && lo1 != null) {
                Lo = lo0 + lo1;
              }
              if (hi0 != null && hi1 != null) {
                Hi = hi0 + hi1 - 1;
              }
              break;
            case BinaryOperator.Opcode.Sub:
              if (lo0 != null && hi1 != null) {
                Lo = lo0 - hi1 + 1;
              }
              if (hi0 != null && lo1 != null) {
                Hi = hi0 - lo1;
              }
              break;
            case BinaryOperator.Opcode.Mul:
              // this uses an incomplete approximation that could be tightened up
              if (lo0 != null && lo1 != null) {
                if (0 <= (BigInteger)lo0 && 0 <= (BigInteger)lo1) {
                  Lo = lo0 * lo1;
                  Hi = hi0 == null || hi1 == null ? null : (hi0 - 1) * (hi1 - 1) + 1;
                } else if ((BigInteger)lo0 < 0 && (BigInteger)lo1 < 0) {
                  Lo = null;  // approximation
                  Hi = lo0 * lo1 + 1;
                }
              }
              break;
            case BinaryOperator.Opcode.Div:
              // this uses an incomplete approximation that could be tightened up
              if (lo0 != null && lo1 != null && 0 <= (BigInteger)lo0 && 0 <= (BigInteger)lo1) {
                Lo = new BigInteger(0);
                Hi = hi0;
              }
              break;
            case BinaryOperator.Opcode.Mod:
              // this uses an incomplete approximation that could be tightened up
              if (lo0 != null && lo1 != null && 0 <= (BigInteger)lo0 && 0 <= (BigInteger)lo1) {
                Lo = new BigInteger(0);
                Hi = hi1;
              }
              break;
            case BinaryOperator.Opcode.RealDiv:
              // this uses an incomplete approximation that could be tightened up
              if (lo0 != null && lo1 != null && 0 <= (BigInteger)lo0 && 0 <= (BigInteger)lo1) {
                Lo = new BigInteger(0);
                Hi = hi1;
              }
              break;
            case BinaryOperator.Opcode.Pow:
              // this uses an incomplete approximation that could be tightened up
              if (lo0 != null && lo1 != null && 0 <= (BigInteger)lo0 && 0 <= (BigInteger)lo1) {
                Lo = new BigInteger(0);
                Hi = hi1;
              }
              break;
            default:
              break;
          }
        } else if (node.Fun is IfThenElse) {
          var op = (IfThenElse)node.Fun;
          Contract.Assert(node.Args.Length == 3);
          BigInteger? guardLo, guardHi, lo0, hi0, lo1, hi1;
          VisitExpr(node.Args[0]);
          guardLo = Lo; guardHi = Hi;
          VisitExpr(node.Args[1]);
          lo0 = Lo; hi0 = Hi;
          VisitExpr(node.Args[2]);
          lo1 = Lo; hi1 = Hi;
          Contract.Assert(guardLo == null || guardHi == null);  // this is a consequence of the guard being boolean
          if (guardLo != null) {
            // guard is always true
            Lo = lo0; Hi = hi0;
          } else if (guardHi != null) {
            // guard is always false
            Lo = lo1; Hi = hi1;
          } else {
            // we don't know which branch will be taken, so join the information from the two branches
            Lo = Node.Min(lo0, lo1, false);
            Hi = Node.Max(hi0, hi1, false);
          }
        }
        return node;
      }
      public override BinderExpr VisitBinderExpr(BinderExpr node) {
        // don't recurse on subexpression
        return node;
      }
      public override Expr VisitOldExpr(OldExpr node) {
        // don't recurse on subexpression
        return node;
      }
      public override Expr VisitCodeExpr(CodeExpr node) {
        // don't recurse on subexpression
        return node;
      }
      public override BvConcatExpr VisitBvConcatExpr(BvConcatExpr node) {
        // don't recurse on subexpression
        return node;
      }
      public override BvExtractExpr VisitBvExtractExpr(BvExtractExpr node) {
        // don't recurse on subexpression
        return node;
      }
    }

    public override Element Eliminate(Element element, Variable v) {
      if (element is E_Bottom) {
        return element;
      }
      var e = (E)element;
      var nn = UpdateOne(e.N, v, null, null);
      if (nn == e.N) {
        return element;
      } else {
        return new E(nn);
      }
    }

    Node UpdateOne(Node nn, Variable v, BigInteger? lo, BigInteger? hi) {
      var orig = nn;
      Node head = null;
      Node prev = null;
      var foundV = false;
      for (; nn != null && !Node.StrictlyBefore(v, nn.V); nn = nn.Next) {
        if (nn.V == v) {
          foundV = true;
          nn = nn.Next;
          break;  // we found the place where the new node goes
        } else {
          var n = new Node(nn.V, nn.Lo, nn.Hi);  // copy this Node
          if (head == null) {
            head = n;
          } else {
            prev.Next = n;
          }
          prev = n;
        }
      }
      Node rest;
      if (lo == null && hi == null) {
        // eliminate all information about "v"
        if (!foundV) {
          return orig;
        }
        rest = nn;
      } else {
        rest = new Node(v, lo, hi);
        rest.Next = nn;
      }
      if (head == null) {
        head = rest;
      } else {
        prev.Next = rest;
      }
      return head;
    }

    /// <summary>
    /// Return a resolved/type-checked expression that represents the conjunction of a and b.
    /// Requires a and b to be resolved and type checked already.
    /// </summary>
    public static Expr BplAnd(Expr a, Expr b) {
      if (a == Expr.True) {
        return b;
      } else if (b == Expr.True) {
        return a;
      } else {
        var nary = Expr.Binary(BinaryOperator.Opcode.And, a, b);
        nary.Type = Type.Bool;
        nary.TypeParameters = SimpleTypeParamInstantiation.EMPTY;
        return nary;
      }
    }

    /// <summary>
    /// Return a resolved/type-checked expression that represents a EQUALS b.
    /// Requires a and b to be resolved and type checked already.
    /// </summary>
    public static Expr BplEq(Expr a, Expr b) {
      var e = Expr.Eq(a, b);
      e.Type = Type.Bool;
      return e;
    }

    /// <summary>
    /// Return a resolved/type-checked expression that represents a LESS-EQUAL b.
    /// Requires a and b to be resolved and type checked already.
    /// </summary>
    public static Expr BplLe(Expr a, Expr b) {
      var e = Expr.Le(a, b);
      e.Type = Type.Bool;
      return e;
    }
    /// <summary>
    /// Return a resolved/type-checked expression that represents a LESS b.
    /// Requires a and b to be resolved and type checked already.
    /// </summary>
    public static Expr BplLt(Expr a, Expr b) {
      var e = Expr.Lt(a, b);
      e.Type = Type.Bool;
      return e;
    }
  }

  public class ThresholdFinder : StandardVisitor
  {
    readonly Implementation Impl;
    public ThresholdFinder(Implementation impl) {
      Contract.Requires(impl != null);
      Impl = impl;
    }
    HashSet<BigInteger> downs = new HashSet<BigInteger>();
    HashSet<BigInteger> ups = new HashSet<BigInteger>();
    public void Find(out List<BigInteger> downThresholds, out List<BigInteger> upThresholds) {
      // always include -1, 0, 1 as down-thresholds
      downs.Clear();
      downs.Add(-1);
      downs.Add(0);
      downs.Add(1);
      // always include 0 and 1 as up-thresholds
      ups.Clear();
      ups.Add(0);
      ups.Add(1);

      foreach (Requires p in Impl.Proc.Requires) {
        Visit(p.Condition);
      }
      foreach (Ensures p in Impl.Proc.Ensures) {
        Visit(p.Condition);
      }
      foreach (var b in Impl.Blocks) {
        foreach (Cmd c in b.Cmds) {
          Visit(c);
        }
      }

      // convert the HashSets to sorted Lists and return
      downThresholds = new List<BigInteger>();
      foreach (var i in downs) {
        downThresholds.Add(i);
      }
      downThresholds.Sort();
      upThresholds = new List<BigInteger>();
      foreach (var i in ups) {
        upThresholds.Add(i);
      }
      upThresholds.Sort();
    }

    public override Expr VisitNAryExpr(NAryExpr node) {
      if (node.Fun is BinaryOperator) {
        var op = (BinaryOperator)node.Fun;
        Contract.Assert(node.Args.Length == 2);
        var arg0 = node.Args[0];
        var arg1 = node.Args[1];
        BigInteger? k;
        switch (op.Op) {
          case BinaryOperator.Opcode.Eq:
          case BinaryOperator.Opcode.Neq:
            k = AsIntLiteral(arg0);
            if (k != null) {
              var i = (BigInteger)k;
              downs.Add(i - 1);
              downs.Add(i);
              ups.Add(i + 1);
              ups.Add(i + 2);
            }
            k = AsIntLiteral(arg1);
            if (k != null) {
              var i = (BigInteger)k;
              downs.Add(i - 1);
              downs.Add(i);
              ups.Add(i + 1);
              ups.Add(i + 2);
            }
            break;
          case BinaryOperator.Opcode.Le:
            k = AsIntLiteral(arg0);
            if (k != null) {
              var i = (BigInteger)k;
              downs.Add(i - 1);
              downs.Add(i);
            }
            k = AsIntLiteral(arg1);
            if (k != null) {
              var i = (BigInteger)k;
              ups.Add(i + 1);
              ups.Add(i + 2);
            }
            break;
          case BinaryOperator.Opcode.Lt:
            k = AsIntLiteral(arg0);
            if (k != null) {
              var i = (BigInteger)k;
              downs.Add(i);
              downs.Add(i + 1);
            }
            k = AsIntLiteral(arg1);
            if (k != null) {
              var i = (BigInteger)k;
              ups.Add(i);
              ups.Add(i + 1);
            }
            break;
          case BinaryOperator.Opcode.Ge:
            { var tmp = arg0; arg0 = arg1; arg1 = tmp; }
            goto case BinaryOperator.Opcode.Le;
          case BinaryOperator.Opcode.Gt:
            { var tmp = arg0; arg0 = arg1; arg1 = tmp; }
            goto case BinaryOperator.Opcode.Lt;
          default:
            break;
        }
      }
      return base.VisitNAryExpr(node);
    }

    BigInteger? AsIntLiteral(Expr e) {
      var lit = e as LiteralExpr;
      if (lit != null && lit.isBigNum) {
        BigNum bn = lit.asBigNum;
        return bn.ToBigInteger;
      }
      return null;
    }
  }

}
